/*
   This example code is in the Public Domain (or CC0 licensed, at your option.)

   Unless required by applicable law or agreed to in writing, this
   software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
   CONDITIONS OF ANY KIND, either express or implied.
*/


/****************************************************************************
* This is a demo for bluetooth config wifi connection to ap. You can config ESP32 to connect a softap
* or config ESP32 as a softap to be connected by other device. APP can be downloaded from github
* android source code: https://github.com/EspressifApp/EspBlufi
* iOS source code: https://github.com/EspressifApp/EspBlufiForiOS
****************************************************************************/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/event_groups.h"
#include "esp_system.h"
#include "esp_wifi.h"
#include "esp_event.h"
#include "esp_log.h"
#include "nvs_flash.h"
#include "esp_bt.h"

#include "esp_blufi_api.h"
#include "blufi_example.h"

#include "esp_blufi.h"

static void example_event_callback(esp_blufi_cb_event_t event, esp_blufi_cb_param_t *param);

#define WIFI_LIST_NUM   10

static wifi_config_t sta_config;
static wifi_config_t ap_config;

/* FreeRTOS event group to signal when we are connected & ready to make a request */
static EventGroupHandle_t wifi_event_group;

/* The event group allows multiple bits for each event,
   but we only care about one event - are we connected
   to the AP with an IP? */
const int CONNECTED_BIT = BIT0;

/* store the station info for send back to phone */
static bool gl_sta_connected = false;    //标记WIFI是否被连接上
static bool ble_is_connected = false;    //标记BLE是否被连接上
static uint8_t gl_sta_bssid[6];          //mac地址
static uint8_t gl_sta_ssid[32];          //ap的wifi名称
static int gl_sta_ssid_len;              //ap的wifi名称长度


static void ip_event_handler(void* arg, esp_event_base_t event_base,int32_t event_id, void* event_data)
{
    wifi_mode_t mode;

    switch (event_id) 
    {
        case IP_EVENT_STA_GOT_IP:    //获取到IP地址表示联网成功
        {
            esp_blufi_extra_info_t info;

            xEventGroupSetBits(wifi_event_group, CONNECTED_BIT);   //置一个连接成功标志位
            esp_wifi_get_mode(&mode);

            memset(&info, 0, sizeof(esp_blufi_extra_info_t));   
            memcpy(info.sta_bssid, gl_sta_bssid, 6);    //wifi ap 的mac地址
            info.sta_bssid_set = true;      //标记已经设置了 bssid
            info.sta_ssid = gl_sta_ssid;    //ssid的名称
            info.sta_ssid_len = gl_sta_ssid_len;   //ssid的长度

            if (ble_is_connected == true)   //如果BLE连接成功，且wifi已经获取IP 成功，则向blufi报告连接成功事件
            {
                esp_blufi_send_wifi_conn_report(mode, ESP_BLUFI_STA_CONN_SUCCESS, 0, &info);
            } 
            else 
            {
                BLUFI_INFO("BLUFI BLE is not connected yet\n");
            }
            break;
        }
        default:break;
    }
    return;
}

static void wifi_event_handler(void* arg, esp_event_base_t event_base,int32_t event_id, void* event_data)
{
    wifi_event_sta_connected_t *event;
    wifi_mode_t mode;

    switch (event_id) 
    {
        case WIFI_EVENT_STA_START:   //STA创建成功后，开始连接wifi
            esp_wifi_connect();
            break;

        case WIFI_EVENT_STA_CONNECTED:   //WIFI STA已被连接上
            gl_sta_connected = true;

            event = (wifi_event_sta_connected_t*) event_data;
            memcpy(gl_sta_bssid, event->bssid, 6);  //ap接入点的mac地址
            memcpy(gl_sta_ssid, event->ssid, event->ssid_len);//ap接入点的名称
            gl_sta_ssid_len = event->ssid_len;   //接入点的名称长度

            break;

        case WIFI_EVENT_STA_DISCONNECTED:   //WIFI STA断开连接
            /* This is a workaround as ESP32 WiFi libs don't currently
            auto-reassociate. */
            gl_sta_connected = false;    //WIFI 连接已断开
            memset(gl_sta_ssid, 0, 32);   //清除记录到的信息
            memset(gl_sta_bssid, 0, 6);
            gl_sta_ssid_len = 0;

            esp_wifi_connect();   //重新启动连接
            xEventGroupClearBits(wifi_event_group, CONNECTED_BIT);

            break;

        case WIFI_EVENT_AP_START:      //softap创建成功后
            esp_wifi_get_mode(&mode);

            /* TODO: get config or information of softap, then set to report extra_info */
            if (ble_is_connected == true) 
            {
                if (gl_sta_connected)  //如果ble已经连接，且sta也已经连接
                {
                    esp_blufi_send_wifi_conn_report(mode, ESP_BLUFI_STA_CONN_SUCCESS, 0, NULL);  //向blufi报告连接信息
                } 
                else 
                {
                    esp_blufi_send_wifi_conn_report(mode, ESP_BLUFI_STA_CONN_FAIL, 0, NULL);
                }
            } 
            else 
            {
                BLUFI_INFO("BLUFI BLE is not connected yet\n");
            }
            break;

        case WIFI_EVENT_SCAN_DONE: 
        {
            uint16_t apCount = 0;
            esp_wifi_scan_get_ap_num(&apCount);
            if (apCount == 0) {
                BLUFI_INFO("Nothing AP found");
                break;
            }
            wifi_ap_record_t *ap_list = (wifi_ap_record_t *)malloc(sizeof(wifi_ap_record_t) * apCount);
            if (!ap_list) {
                BLUFI_ERROR("malloc error, ap_list is NULL");
                break;
            }
            ESP_ERROR_CHECK(esp_wifi_scan_get_ap_records(&apCount, ap_list));
            esp_blufi_ap_record_t * blufi_ap_list = (esp_blufi_ap_record_t *)malloc(apCount * sizeof(esp_blufi_ap_record_t));
            if (!blufi_ap_list) {
                if (ap_list) {
                    free(ap_list);
                }
                BLUFI_ERROR("malloc error, blufi_ap_list is NULL");
                break;
            }
            for (int i = 0; i < apCount; ++i)
            {
                blufi_ap_list[i].rssi = ap_list[i].rssi;
                memcpy(blufi_ap_list[i].ssid, ap_list[i].ssid, sizeof(ap_list[i].ssid));
            }

            if (ble_is_connected == true) {
                esp_blufi_send_wifi_list(apCount, blufi_ap_list);
            } else {
                BLUFI_INFO("BLUFI BLE is not connected yet\n");
            }

            esp_wifi_scan_stop();
            free(ap_list);
            free(blufi_ap_list);
            break;
        }
        default:break;
    }
    return;
}

//初始化WiFi相关事件和配置参数
static void initialise_wifi(void)
{
    ESP_ERROR_CHECK(esp_netif_init());
    wifi_event_group = xEventGroupCreate();
    ESP_ERROR_CHECK(esp_event_loop_create_default());
    esp_netif_t *sta_netif = esp_netif_create_default_wifi_sta();
    assert(sta_netif);
    ESP_ERROR_CHECK(esp_event_handler_register(WIFI_EVENT, ESP_EVENT_ANY_ID, &wifi_event_handler, NULL));  //wifi事件处理
    ESP_ERROR_CHECK(esp_event_handler_register(IP_EVENT, IP_EVENT_STA_GOT_IP, &ip_event_handler, NULL));   //ip事件处理

    wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();    //使用默认参数配置wifi
    ESP_ERROR_CHECK( esp_wifi_init(&cfg) );
    ESP_ERROR_CHECK( esp_wifi_set_mode(WIFI_MODE_STA) );    //wifi配置为STA模式
    ESP_ERROR_CHECK( esp_wifi_start() );
}

//注册BluFi的回调函数
static esp_blufi_callbacks_t example_callbacks = {
    .event_cb = example_event_callback,       //BluFi事件处理函数
    .negotiate_data_handler = blufi_dh_negotiate_data_handler,  //DH 算法密钥协商函数
    .encrypt_func = blufi_aes_encrypt,     //blufi加密函数
    .decrypt_func = blufi_aes_decrypt,     //blufi解密函数
    .checksum_func = blufi_crc_checksum,   //blufi校验和函数
};


//blufi事件的处理回调函数
static void example_event_callback(esp_blufi_cb_event_t event, esp_blufi_cb_param_t *param)
{
    /* actually, should post to blufi_task handle the procedure,
     * now, as a example, we do it more simply */
    switch (event) {       
    case ESP_BLUFI_EVENT_INIT_FINISH:      //BLUEFI初始化完成事件
        BLUFI_INFO("BLUFI init finish\n");
        esp_blufi_adv_start();      //esp_blufi启动ble广播事件，在里面可以配置广播信息
        break;
    case ESP_BLUFI_EVENT_DEINIT_FINISH:    //BLUEFI解除初始化事件
        BLUFI_INFO("BLUFI deinit finish\n");
        break;
    case ESP_BLUFI_EVENT_BLE_CONNECT:      //BLE 建立连接事件
        BLUFI_INFO("BLUFI ble connect\n");
        ble_is_connected = true;     //BLE连接成功后，
        esp_blufi_adv_stop();        //停止广播，
        blufi_security_init();       //初始化BLUFI加密相关的内容
        break;
    case ESP_BLUFI_EVENT_BLE_DISCONNECT:    //BLE 断开连接事件
        BLUFI_INFO("BLUFI ble disconnect\n");
        ble_is_connected = false;    //BLE断开连接后
        blufi_security_deinit();     //取消加密相关的内容
        esp_blufi_adv_start();       //重新启动BLUFI广播
        break;
    case ESP_BLUFI_EVENT_SET_WIFI_OPMODE:      //设置WIFI 进入运行模式
        BLUFI_INFO("BLUFI Set WIFI opmode %d\n", param->wifi_mode.op_mode);
        ESP_ERROR_CHECK( esp_wifi_set_mode(param->wifi_mode.op_mode) );
        break;
    case ESP_BLUFI_EVENT_REQ_CONNECT_TO_AP:   //设置断开原有的WiFi连接，并连接指定WiFi；
        BLUFI_INFO("BLUFI requset wifi connect to AP\n");
        /* there is no wifi callback when the device has already connected to this wifi
        so disconnect wifi before connection.
        */
        esp_wifi_disconnect();
        esp_wifi_connect();
        break;
    case ESP_BLUFI_EVENT_REQ_DISCONNECT_FROM_AP:   //断开当前WiIFi连接到的AP；
        BLUFI_INFO("BLUFI requset wifi disconnect from AP\n");
        esp_wifi_disconnect();
        break;
    case ESP_BLUFI_EVENT_REPORT_ERROR:
        BLUFI_ERROR("BLUFI report error, error code %d\n", param->report_error.state);
        esp_blufi_send_error_info(param->report_error.state);
        break;
    case ESP_BLUFI_EVENT_GET_WIFI_STATUS:   //获取WiFi状态信息，包括：WiFi当前模式、以及是否连接成功；
    {
        wifi_mode_t mode;
        esp_blufi_extra_info_t info;

        esp_wifi_get_mode(&mode);

        if (gl_sta_connected) 
        {
            memset(&info, 0, sizeof(esp_blufi_extra_info_t));
            memcpy(info.sta_bssid, gl_sta_bssid, 6);    //获取到bssid
            info.sta_bssid_set = true;
            info.sta_ssid = gl_sta_ssid;    //获取到ssid
            info.sta_ssid_len = gl_sta_ssid_len;
            esp_blufi_send_wifi_conn_report(mode, ESP_BLUFI_STA_CONN_SUCCESS, 0, &info);  //报告WIFI连接成功
        } 
        else 
        {
            esp_blufi_send_wifi_conn_report(mode, ESP_BLUFI_STA_CONN_FAIL, 0, NULL);
        }
        BLUFI_INFO("BLUFI get wifi status from AP\n");

        break;
    }
    case ESP_BLUFI_EVENT_RECV_SLAVE_DISCONNECT_BLE:   //关闭blufi的gatt服务连接；
        BLUFI_INFO("blufi close a gatt connection");
        esp_blufi_disconnect();
        break;
    case ESP_BLUFI_EVENT_DEAUTHENTICATE_STA:
        /* TODO */
        break;
	case ESP_BLUFI_EVENT_RECV_STA_BSSID:   //设置进入STA模式，获取目标AP的bssid；
        memcpy(sta_config.sta.bssid, param->sta_bssid.bssid, 6);
        sta_config.sta.bssid_set = 1;
        esp_wifi_set_config(WIFI_IF_STA, &sta_config);
        BLUFI_INFO("Recv STA BSSID %s\n", sta_config.sta.ssid);
        break;
	case ESP_BLUFI_EVENT_RECV_STA_SSID:   //设置进入STA模式，获取目标AP的WiFi账号；
        strncpy((char *)sta_config.sta.ssid, (char *)param->sta_ssid.ssid, param->sta_ssid.ssid_len);
        sta_config.sta.ssid[param->sta_ssid.ssid_len] = '\0';
        esp_wifi_set_config(WIFI_IF_STA, &sta_config);
        BLUFI_INFO("Recv STA SSID %s\n", sta_config.sta.ssid);
        break;
	case ESP_BLUFI_EVENT_RECV_STA_PASSWD:   //设置进入STA模式，获取目标AP的WiFi密码；
        strncpy((char *)sta_config.sta.password, (char *)param->sta_passwd.passwd, param->sta_passwd.passwd_len);
        sta_config.sta.password[param->sta_passwd.passwd_len] = '\0';
        esp_wifi_set_config(WIFI_IF_STA, &sta_config);
        BLUFI_INFO("Recv STA PASSWORD %s\n", sta_config.sta.password);
        break;
	case ESP_BLUFI_EVENT_RECV_SOFTAP_SSID:  //设置进入Soft AP模式，获取AP自定义账号；
        strncpy((char *)ap_config.ap.ssid, (char *)param->softap_ssid.ssid, param->softap_ssid.ssid_len);
        ap_config.ap.ssid[param->softap_ssid.ssid_len] = '\0';
        ap_config.ap.ssid_len = param->softap_ssid.ssid_len;
        esp_wifi_set_config(WIFI_IF_AP, &ap_config);
        BLUFI_INFO("Recv SOFTAP SSID %s, ssid len %d\n", ap_config.ap.ssid, ap_config.ap.ssid_len);
        break;
	case ESP_BLUFI_EVENT_RECV_SOFTAP_PASSWD:  //设置进入Soft AP模式，获取AP自定义密码；
        strncpy((char *)ap_config.ap.password, (char *)param->softap_passwd.passwd, param->softap_passwd.passwd_len);
        ap_config.ap.password[param->softap_passwd.passwd_len] = '\0';
        esp_wifi_set_config(WIFI_IF_AP, &ap_config);
        BLUFI_INFO("Recv SOFTAP PASSWORD %s len = %d\n", ap_config.ap.password, param->softap_passwd.passwd_len);
        break;
	case ESP_BLUFI_EVENT_RECV_SOFTAP_MAX_CONN_NUM:   //设置Soft AP模式下最大可连接设备数量；
        if (param->softap_max_conn_num.max_conn_num > 4) {
            return;
        }
        ap_config.ap.max_connection = param->softap_max_conn_num.max_conn_num;
        esp_wifi_set_config(WIFI_IF_AP, &ap_config);
        BLUFI_INFO("Recv SOFTAP MAX CONN NUM %d\n", ap_config.ap.max_connection);
        break;
	case ESP_BLUFI_EVENT_RECV_SOFTAP_AUTH_MODE:   //设置Soft AP模式下进入认证模式；
        if (param->softap_auth_mode.auth_mode >= WIFI_AUTH_MAX) {
            return;
        }
        ap_config.ap.authmode = param->softap_auth_mode.auth_mode;
        esp_wifi_set_config(WIFI_IF_AP, &ap_config);
        BLUFI_INFO("Recv SOFTAP AUTH MODE %d\n", ap_config.ap.authmode);
        break;
	case ESP_BLUFI_EVENT_RECV_SOFTAP_CHANNEL:   //设置Soft AP模式下的通讯通道；
        if (param->softap_channel.channel > 13) {
            return;
        }
        ap_config.ap.channel = param->softap_channel.channel;
        esp_wifi_set_config(WIFI_IF_AP, &ap_config);
        BLUFI_INFO("Recv SOFTAP CHANNEL %d\n", ap_config.ap.channel);
        break;
    case ESP_BLUFI_EVENT_GET_WIFI_LIST:   //获取扫描到的空中WiFi账号、通信通道以及站点MAC地址；
    {
        wifi_scan_config_t scanConf = {
            .ssid = NULL,
            .bssid = NULL,
            .channel = 0,
            .show_hidden = false
        };
        esp_wifi_scan_start(&scanConf, true);
        break;
    }
    case ESP_BLUFI_EVENT_RECV_CUSTOM_DATA:    //将接收到的数据打印出来；
        BLUFI_INFO("Recv Custom Data %d\n", param->custom_data.data_len);
        esp_log_buffer_hex("Custom Data", param->custom_data.data, param->custom_data.data_len);

        esp_blufi_send_custom_data(param->custom_data.data, param->custom_data.data_len);  //通过BLE把接收到的数据回传到手机端

        break;
	case ESP_BLUFI_EVENT_RECV_USERNAME:
        /* Not handle currently */
        break;
	case ESP_BLUFI_EVENT_RECV_CA_CERT:
        /* Not handle currently */
        break;
	case ESP_BLUFI_EVENT_RECV_CLIENT_CERT:
        /* Not handle currently */
        break;
	case ESP_BLUFI_EVENT_RECV_SERVER_CERT:
        /* Not handle currently */
        break;
	case ESP_BLUFI_EVENT_RECV_CLIENT_PRIV_KEY:
        /* Not handle currently */
        break;;
	case ESP_BLUFI_EVENT_RECV_SERVER_PRIV_KEY:
        /* Not handle currently */
        break;
    default:
        break;
    }
}

void app_main(void)
{
    esp_err_t ret;

    //初始化NVS存储器
    ret = nvs_flash_init();
    if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
        ESP_ERROR_CHECK(nvs_flash_erase());
        ret = nvs_flash_init();
    }
    ESP_ERROR_CHECK( ret );

    initialise_wifi();    //初始化WIFI，使用默认参数配置wifi，即使wifi没有配网信息，但也可以使用默认参数进行配置

    ESP_ERROR_CHECK(esp_bt_controller_mem_release(ESP_BT_MODE_CLASSIC_BT));  //释放控制器的内存

    esp_bt_controller_config_t bt_cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT();  //使用默认参数来配置BLE
    ret = esp_bt_controller_init(&bt_cfg);   //esp蓝牙控制器初始化，此函数只能被调用一次，且必须在其他蓝牙功能被调用之前调用
    if (ret) {
        BLUFI_ERROR("%s initialize bt controller failed: %s\n", __func__, esp_err_to_name(ret));
    }

    //使能蓝牙控制器，mode是蓝牙模式，
    //如果想要动态改变蓝牙模式不能直接调用该函数，应该先用disable关闭蓝牙再使用该API来改变蓝牙模式
    ret = esp_bt_controller_enable(ESP_BT_MODE_BLE);    
    if (ret) {
        BLUFI_ERROR("%s enable bt controller failed: %s\n", __func__, esp_err_to_name(ret));
        return;
    }

    ret = esp_blufi_host_init();    //初始化蓝牙Host   
    if (ret) {
        BLUFI_ERROR("%s initialise host failed: %s\n", __func__, esp_err_to_name(ret));
        return;
    }

    BLUFI_INFO("BLUFI VERSION %04x\n", esp_blufi_get_version());   //获取BLUFI的版本

    ret = esp_blufi_register_callbacks(&example_callbacks);    //注册回调函数
    if(ret){
        BLUFI_ERROR("%s blufi register failed, error code = %x\n", __func__, ret);
        return;
    }

    ret = esp_blufi_gap_register_callback();    //注册蓝牙GAP，在里面处理BLE GAP的信息
    if(ret){
        BLUFI_ERROR("%s gap register failed, error code = %x\n", __func__, ret);
        return;
    }
}
